﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace WWInternal
{
    class TerrainTileInfo : MonoBehaviour
    {
        public Mesh mesh { get; set; }
        public Vector2Int position { get; set; }
        public MeshFilter filter;
        public MeshRenderer render;

        public void GoodToGo()
        {
            filter = gameObject.GetComponent<MeshFilter>();
            if (filter == null)
            {
                filter = gameObject.AddComponent<MeshFilter>();
            }
            filter.mesh = mesh;
        }
    }
}



public class WWTerrainGenerator : MonoBehaviour
{
    public enum TerrainType
    {
        GrassLand,
        Forest,
        Water,
        Rock,
        Snow
    };

    public enum TerrainStatus
    {
        _ShiftBegin = 16,
        Burnt = 1 << (_ShiftBegin),
        Onfire = 1 << (_ShiftBegin + 1),
        Occupied = 1 << (_ShiftBegin + 2),
    }

    // 至少在我的 GPU 上超过 256 就会出问题
    [Range(1, 256)]
    public int ChunkDivisions = 64; // 生成顶点数量 ChunkSize * ChunkSize 的 Perlin noise
    public float ChunkWidth = 4f;
    public float ChunkLength = 4f;
    public float BaseHeight = -0.3f;

    public float PlainThreshold;


    public int RandSeed = 0x32974329; // 随机种子
    
    [Range(0.0f, 20.0f)]
    public float Frequency = 1.0f;
    public float Amplitute = 1;

    public Vector2 ShiftTileCoeff = new Vector2(1, 1);

    public Vector3 StartPosition = new Vector3(0.0f, 0.0f, 0.0f);

    // 谐波 0 的偏移
    public Vector3 HarmonicOffset0 = new Vector3(0.0f, 0.0f, 0.0f);
    public Vector3 HarmonicOffset1 = new Vector3(0.0f, 0.0f, 0.0f);
    public Vector3 HarmonicOffset2 = new Vector3(0.0f, 0.0f, 0.0f);
    public Vector3 HarmonicOffset3 = new Vector3(0.0f, 0.0f, 0.0f);
    public Vector3 HarmonicOffset4 = new Vector3(0.0f, 0.0f, 0.0f);

    public Material TerrainMaterial;
    public Material DebugHeightMapMaterial;

    public ComputeShader TerrainCS;

    RenderTexture HeightMapRT;

    int kernelSampleSimplexFBMNoise;

    private int[] vertIndexs;		//网格三角形索引
    private Vector3[] positions;    //位置
    private Vector2[] uvs; 			//uv坐标
    private Mesh mesh;
    private MeshFilter filter;
    private MeshRenderer render;

    int lastChunkDivisions;
    float lastChunkWidth;
    float lastChunkLength;
    float lastBaseHeight;

    int lastRandSeed;
    float lastFrequency = 1.0f;
    float lastAmplitute = 1;
    Vector3 lastStartPosition;
    Vector2 lastShiftTileCoeff;

    Vector3 lastHarmonicOffset0;
    Vector3 lastHarmonicOffset1;
    Vector3 lastHarmonicOffset2;


    List<WWInternal.TerrainTileInfo> renderingTiles = new List<WWInternal.TerrainTileInfo>();
    List<GameObject> children = new List<GameObject>();

    void RenderSquareChunks(Vector2Int center, int size)
    {
        
    }
    

    Vector3 CloneVector(Vector3 v)
    {
        return new Vector3(v.x, v.y, v.z);
    }

    void blitData()
    {
        lastChunkDivisions = ChunkDivisions;
        lastChunkWidth = ChunkWidth;
        lastChunkLength = ChunkLength;
        lastBaseHeight = BaseHeight;
        lastRandSeed = RandSeed;
        lastFrequency = Frequency;
        lastAmplitute = Amplitute;

        lastStartPosition = CloneVector(StartPosition);
        lastHarmonicOffset0 = CloneVector(HarmonicOffset0);
        lastHarmonicOffset1 = CloneVector(HarmonicOffset1);
        lastHarmonicOffset2 = CloneVector(HarmonicOffset2);
        lastShiftTileCoeff = CloneVector(ShiftTileCoeff);
    }

    bool MeshNeedsUpdate()
    {
        return ChunkDivisions != lastChunkDivisions;
    }

    bool TerrainNeedsUpdate()
    {
        return lastChunkDivisions != ChunkDivisions
            || lastChunkWidth != ChunkWidth
            || lastChunkLength != ChunkLength
            || lastBaseHeight != BaseHeight
            || lastRandSeed != RandSeed
            || lastFrequency != Frequency
            || lastAmplitute != Amplitute
            || lastStartPosition != StartPosition
            || lastHarmonicOffset0 != HarmonicOffset0
            || lastHarmonicOffset1 != HarmonicOffset1
            || lastHarmonicOffset2 != HarmonicOffset2

            || lastShiftTileCoeff != ShiftTileCoeff
            ;
    }

    RenderTexture CreateRenderTexture(int size, RenderTextureFormat type = RenderTextureFormat.ARGBFloat)
    {
        RenderTexture rt = new RenderTexture(size, size, 0, type);
        rt.enableRandomWrite = true;
        rt.Create();
        return rt;

    }

    void initComputeShaderRenderTexture()
    {
        if (HeightMapRT != null && HeightMapRT.IsCreated())
        {
            HeightMapRT.Release();
        }


        HeightMapRT = CreateRenderTexture(ChunkDivisions, RenderTextureFormat.RFloat);
    }

    void initComputeShader()
    {

        initComputeShaderRenderTexture();

        kernelSampleSimplexFBMNoise = TerrainCS.FindKernel("SampleSimplexFBMNoise");

        updateCSData();
    }

    void updateCSData()
    {
        TerrainCS.SetInt("RandSeed", RandSeed);
        TerrainCS.SetFloat("Frequency", Frequency);
        TerrainCS.SetFloat("Amplitute", Amplitute);
        TerrainCS.SetFloat("BaseHeight", BaseHeight);
        TerrainCS.SetFloat("ChunkDivisions", ChunkDivisions);
        
        TerrainCS.SetFloat("ChunkWidth", ChunkWidth);
        TerrainCS.SetFloat("ChunkLength", ChunkLength);
        TerrainCS.SetVector("StartPosition", StartPosition);

        TerrainCS.SetVector("HarmonicOffset0", HarmonicOffset0);
        TerrainCS.SetVector("HarmonicOffset1", HarmonicOffset1);
        TerrainCS.SetVector("HarmonicOffset2", HarmonicOffset2);
        TerrainCS.SetVector("HarmonicOffset3", HarmonicOffset3);
        TerrainCS.SetVector("HarmonicOffset4", HarmonicOffset4);

        TerrainCS.SetTexture(kernelSampleSimplexFBMNoise, "HeightMapRT", HeightMapRT);
    }

    private void Awake()
    {
        filter = gameObject.GetComponent<MeshFilter>();
        if (filter == null)
        {
            filter = gameObject.AddComponent<MeshFilter>();
        }
        render = gameObject.GetComponent<MeshRenderer>();
        if (render == null)
        {
            render = gameObject.AddComponent<MeshRenderer>();
        }

        mesh = new Mesh();
        filter.mesh = mesh;
        render.material = TerrainMaterial;
    }

    void runComputeShader()
    {
        if (ChunkDivisions < 8) return; 
        TerrainCS.Dispatch(kernelSampleSimplexFBMNoise, ChunkDivisions / 8, ChunkDivisions / 8, 1);
    }

    void LiveUpdateCSInEditor()
    {
        if (!Application.isEditor) return;
        if (ChunkDivisions < 8) return;

        if (TerrainNeedsUpdate())
        {

            bool meshUpdate = MeshNeedsUpdate();
            blitData();
            updateCSData();
            if (meshUpdate)
            {
                initComputeShaderRenderTexture();
            }
            // runComputeShader();

            if(meshUpdate)
            {
                foreach (var x in renderingTiles)
                {
                    ComputeHeightMap(x);
                }
                //CombineTilesToMesh();
            }
            else
            {
                foreach (var x in renderingTiles)
                {
                    UpdateOneTileVertices(x);
                }
                //CombineTilesToMesh();
            }

            // UpdateMeshVertices();
        }
    }

    Vector3 GetTranslate(WWInternal.TerrainTileInfo info)
    {
        var newTranslate = new Vector3(info.position.x, 0, info.position.y);
        newTranslate.x *= ChunkLength;
        newTranslate.z *= ChunkWidth;
        return newTranslate;
    }

    void CombineTilesToMesh()
    {
        var combine = new CombineInstance[renderingTiles.Count];
        int i = 0;
        foreach(var info in renderingTiles)
        {
            
            combine[i].mesh = info.mesh;

            // var newTranslate = GetTranslate(info);

            combine[i].transform = Matrix4x4.TRS(new Vector3(0, 0, 0), Quaternion.Euler(0, 0, 0), new Vector3(1, 1, 1));

            ++i;
        }

        mesh = new Mesh();
        filter.mesh = mesh;
        filter.mesh.CombineMeshes(combine);
    }

    void ComputeHeightMap(WWInternal.TerrainTileInfo info)
    {
        var off = info.position * ChunkDivisions;
        //var off = info.position;
        var newStart = StartPosition + new Vector3(off.y, off.x, 0);
        

        TerrainCS.SetVector("StartPosition", newStart);
        runComputeShader();

        var newTranslate = GetTranslate(info);

        info.mesh = CreateMesh(ChunkDivisions, newTranslate);
        info.GoodToGo();
    }

    void UpdateOneTileVertices(WWInternal.TerrainTileInfo info)
    {
        var off = info.position * ChunkDivisions;
        // var off = info.position * ChunkDivisions * ShiftTileCoeff;
        var newStart = StartPosition + new Vector3(off.y, off.x, 0);

        TerrainCS.SetVector("StartPosition", newStart);

        runComputeShader();

        UpdateMeshVertices(info);
    }

    void InitTestData()
    {
        for (int i = -1; i < 2; ++i)
        {
            for (int j = -1; j < 2; ++j)
            {
                var obj = new GameObject( "tile(" + i + ", "+ j + ")" );
                
                var t = obj.gameObject.AddComponent<WWInternal.TerrainTileInfo>();
                t.filter = obj.gameObject.GetComponent<MeshFilter>();
                t.mesh = null;
                // t.render = render;
                t.render = obj.gameObject.GetComponent<MeshRenderer>();
                if (t.render == null)
                {
                    t.render = obj.gameObject.AddComponent<MeshRenderer>();
                }
                t.render.material = TerrainMaterial;
                t.position = new Vector2Int(i, j);

                renderingTiles.Add(t);
                obj.SetActive(true);
                obj.transform.parent = transform;
                children.Add(obj);
            }
        }
    }

    // Start is called before the first frame update
    void Start()
    {

        InitTestData();

        blitData();
        initComputeShader();
        foreach (var x in renderingTiles)
        {
            ComputeHeightMap(x);
        }
        // CombineTilesToMesh();
        //UpdateMeshVertices();
    }

    
    void UpdateMeshVertices(WWInternal.TerrainTileInfo info)
    {

        Texture2D tmp_texture_2d = new Texture2D(ChunkDivisions, ChunkDivisions, TextureFormat.RFloat, false);

        var lastRT = RenderTexture.active;
        RenderTexture.active = HeightMapRT;
        Rect rect = new Rect(0, 0, ChunkDivisions, ChunkDivisions);

        tmp_texture_2d.ReadPixels(rect, 0, 0);
        int MeshSize = ChunkDivisions;

        Vector3[] positions = new Vector3[MeshSize * MeshSize];

        var trans = GetTranslate(info);

        for (int i = 0; i < MeshSize; i++)
        {
            for (int j = 0; j < MeshSize; j++)
            {
                int index = i * MeshSize + j;
                positions[index] = new Vector3(
                    (j - MeshSize / 2.0f) * ChunkLength / MeshSize + trans.x,
                    tmp_texture_2d.GetPixel(i, j).r + trans.y,
                    (i - MeshSize / 2.0f) * ChunkWidth / MeshSize + trans.z);
            }
        }
        info.mesh.vertices = positions;
        //mesh.SetIndices(vertIndexs, MeshTopology.Triangles, 0);
        //mesh.uv = uvs;
        info.mesh.RecalculateBounds();
        info.mesh.RecalculateNormals();

        RenderTexture.active = lastRT;
    }

    private Mesh CreateMesh(int MeshSize, Vector3 Offset)
    {
        var mesh = new Mesh();

        Texture2D tmp_texture_2d = new Texture2D(ChunkDivisions, ChunkDivisions, TextureFormat.RFloat, false);

        var lastRT = RenderTexture.active;
        RenderTexture.active = HeightMapRT;
        Rect rect = new Rect(0, 0, ChunkDivisions, ChunkDivisions);

        tmp_texture_2d.ReadPixels(rect, 0, 0);


        vertIndexs = new int[(MeshSize - 1) * (MeshSize - 1) * 6];
        positions = new Vector3[MeshSize * MeshSize];
        uvs = new Vector2[MeshSize * MeshSize];

        int inx = 0;
        for (int i = 0; i < MeshSize; i++)
        {
            for (int j = 0; j < MeshSize; j++)
            {
                int index = i * MeshSize + j;
                positions[index] = new Vector3(
                    (j - MeshSize / 2.0f) * ChunkLength / MeshSize + Offset.x,
                    tmp_texture_2d.GetPixel(i, j).r + Offset.y, 
                    (i - MeshSize / 2.0f) * ChunkWidth / MeshSize + Offset.z);
                // positions[index] = new Vector3((j - MeshSize / 2.0f) * ChunkLength / MeshSize, 0, (i - MeshSize / 2.0f) * ChunkWidth / MeshSize);
                uvs[index] = new Vector2(j / (MeshSize - 1.0f), i / (MeshSize - 1.0f));

                if (i != MeshSize - 1 && j != MeshSize - 1)
                {
                    vertIndexs[inx++] = index;
                    vertIndexs[inx++] = index + MeshSize;
                    vertIndexs[inx++] = index + MeshSize + 1;

                    vertIndexs[inx++] = index;
                    vertIndexs[inx++] = index + MeshSize + 1;
                    vertIndexs[inx++] = index + 1;
                }
            }
        }

        mesh.vertices = positions;
        mesh.SetIndices(vertIndexs, MeshTopology.Triangles, 0);
        mesh.uv = uvs;
        mesh.RecalculateBounds();
        mesh.RecalculateNormals();
        RenderTexture.active = lastRT;

        return mesh;
    }

    Vector4 PadVector(Vector3 v, float extra)
    {
        return new Vector4(v.x, v.y, v.z, extra);
    }

    // Update is called once per frame
    void Update()
    {
        LiveUpdateCSInEditor();
        
        TerrainMaterial.SetTexture("_HeightMap", HeightMapRT);
        TerrainMaterial.SetVector("_Translate", PadVector(transform.position, 0));
        TerrainMaterial.SetVector("_Scale", PadVector(transform.localScale, 1));
        if (DebugHeightMapMaterial)
        {
            DebugHeightMapMaterial.SetTexture("_MainTex", HeightMapRT);
        }
    }
}
